package util.oma;

import static util.oma.Mapper.defaultMapper;
import static util.oma.Modifier.APPROXIMATE_USING_SETTERS;
import static util.oma.Modifier.TRANSIENT;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;

public class Example {
    public static class TimeZoneResolver extends Mapper {
        @Override
        protected Object resolve(final Object value) {
            if (TimeZone.class.isAssignableFrom(value.getClass()))
                return ((TimeZone) value).getID();
            return super.resolve(value);
        }
    }
    
    @Approximate(source = "Date")
    public static class Time {
        @Approximate(source = "Date")
        public static class InnerEntity {
            public int hours;
            public int minutes;
        }
        
        @Approximate(path = "cdate")
        public InnerEntity innerEntity;
        @Approximate(path = "cdate.zoneinfo")
        public String timeZone;
        @Approximate(path = "getHours()")
        public int hours;
        @Approximate(path = "getMinutes()")
        public int minutes;
        @Approximate(path = "getSeconds()")
        public int seconds;
        @Approximate(path = "cdate.millis")
        public int milliseconds;
        transient public int transientValue;
        @Approximate(mode = TRANSIENT)
        public String nullable = "try to set null here";
        
        @Override
        public String toString() {
            String result = "\ntimeZone: " + timeZone + "\nhours: " + hours + "\nminutes: " + minutes + "\nseconds: " + seconds + "\nmilliseconds: "
                    + milliseconds + "\ntransientValue: " + transientValue;
            if (innerEntity != null)
                result += "\ninnerHours: " + innerEntity.hours + "\ninnerMinutes: " + innerEntity.minutes;
            return result;
        }
    }
    
    @Approximate(source = { "Date", "Time" })
    public static class Time2 {
        // mapping for Date and default mapping are overridden
        @Approximate(source = "Date", path = { "getHours()", "hours" })
        public int h;
        // map only from Date
        @Approximate(source = "Date", path = { "getMinutes()", ";" })
        public int m;
        // mapping for Date only is overridden. Default mapping by field name is used for Time
        @Approximate(source = "Date", path = "getSeconds()")
        public int seconds;
        // skip for Date, default mapping for Time
        @Approximate(source = "Date", path = ";", nullable = false)
        public String nullable;
        
        @Override
        public String toString() {
            return "\nh: " + h + "\nm: " + m + "\nseconds: " + seconds;
        }
    }
    
    @Approximate(source = "Time", mode = APPROXIMATE_USING_SETTERS)
    public static class Time3 {
        public int h;
        public int m;
        public int s;
        
        @Approximate(path = "hours")
        public void setH(final int h) {
            this.h = h;
        }
        
        @Approximate(source = "Time", path = "minutes")
        public void setM(final int m) {
            this.m = m;
        }
        
        public void setSeconds(final int s) {
            this.s = s;
        }
        
        @SuppressWarnings("unused")
        @Approximate(mode = TRANSIENT)
        public void setSomething(final int value) {
            throw new Error();
        }
        
        @Override
        public String toString() {
            return "\nh: " + h + "\nm: " + m + "\ns: " + s;
        }
        
        public int getSeconds() {
            return s;
        }
        
        public int getH() {
            return h;
        }
        
        public int getM() {
            return m;
        }
    }
    
    public static class ListOfTime {
        List<Time> times = new LinkedList<Example.Time>() {{
            add(new Time() {{
                hours = 1;
                minutes = 2;
                seconds = 3;
            }});
        }};
    }
    
    @Approximate(source = "ListOfTime")
    public static class ListOfTime2 {
        List<Time2> times;
        
        @Override
        public String toString() {
            return times.toString();
        }
    }
    
    @Approximate(source = "ListOfTime")
    public static class ListOfTime22 {
        @Approximate(targetEntity = Time2.class)
        List<Time2> times = new LinkedList<Example.Time2>() {{
            // will not be lost, mapping is incremental
            add(new Time2() {{
                h = 22;
                m = 22;
                seconds = 22;
            }});
        }};
        
        @Override
        public String toString() {
            return times.toString();
        }
    }
    
    public static void main(final String[] args) {
        final Date date = new Date();
        System.out.println(date);
        
        // map Time from Date
        final TimeZoneResolver mapper = new TimeZoneResolver();
        final Time time = mapper.map(new Time(), date);
        System.out.println(time);
        
        // map Time2 from Time
        final Time2 time2 = defaultMapper().map(new Time2(), time);
        System.out.println(time2);
        
        // map Time2 from Date
        final Time2 time22 = defaultMapper().map(new Time2(), date);
        System.out.println(time22);
        
        // map Time3 from Time using setters, non-strict
        final Time3 time3 = defaultMapper().map(new Time3(), time);
        System.out.println(time3);
        
        // map collections
        final ListOfTime2 listOfTime2 = defaultMapper().map(new ListOfTime2(), new ListOfTime());
        System.out.println(listOfTime2);
        
        // map collections
        final ListOfTime22 listOfTime22 = defaultMapper().map(new ListOfTime22(), new ListOfTime());
        System.out.println(listOfTime22);
    }
}
